package close.util;

import java.awt.Point;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Random;

public class Maze {

	private static final Random random = new Random();

	private ArrayList<Point> exposed;
	
	private int[][] maze;
	private Point start;
	
	private int width;
	private int height;
	private int branchRate;
	private boolean noDiagonals;	

	public static final int UNEXPOSED = 0;
	private static final int UNDETERMINED = 1;
	public static final int PATH = 2;
	public static final int WALL = 3;
	public static final int START = 4;
	
	public Maze(int width, int height, int branchRate,  boolean noDiagonals) {
		this.width = width;
		this.height = height;
		this.branchRate = branchRate;
		this.noDiagonals = noDiagonals;
		generateMaze();
	}

	public void generateMaze() {
		maze = new int[width][height];
		exposed = new ArrayList<Point>();

		start = new Point(random.nextInt(width), random.nextInt(height));
		createPath(start.x, start.y);

		while (exposed.size() > 0) {
			double indexFloat = random.nextFloat();
			indexFloat = Math.pow(indexFloat, Math.pow(Math.E, -1 * branchRate));
			int index = (int) (indexFloat * exposed.size());
			if (index >= exposed.size()) {
				index--;
			}
			Point choice = exposed.get(index);
			if (validPath(choice.x, choice.y))
				createPath(choice.x, choice.y);
			else
				createWall(choice.x, choice.y);
			exposed.remove(choice);
		}
		maze[start.x][start.y] = START; // Create start
	}

	private void createPath(int x, int y) {
		ArrayList<Point> newExposed = new ArrayList<Point>();
		maze[x][y] = PATH;

		if (getCell(x - 1, y) == UNEXPOSED) {
			maze[x - 1][y] = UNDETERMINED;
			newExposed.add(new Point(x - 1, y));
		}

		if (getCell(x + 1, y) == UNEXPOSED) {
			maze[x + 1][y] = UNDETERMINED;
			newExposed.add(new Point(x + 1, y));
		}

		if (getCell(x, y - 1) == UNEXPOSED) {
			maze[x][y - 1] = UNDETERMINED;
			newExposed.add(new Point(x, y - 1));
		}
		if (getCell(x, y + 1) == UNEXPOSED) {
			maze[x][y + 1] = UNDETERMINED;
			newExposed.add(new Point(x, y + 1));
		}

		Collections.shuffle(newExposed);
		exposed.addAll(newExposed);
	}

	private void createWall(int x, int y) {
		maze[x][y] = WALL;
	}

	private boolean validPath(int x, int y) {

		int edgeState = 0;

		if (getCell(x - 1, y) == PATH)
			edgeState += 1;

		if (getCell(x + 1, y) == PATH)
			edgeState += 2;

		if (getCell(x, y - 1) == PATH)
			edgeState += 4;

		if (getCell(x, y + 1) == PATH)
			edgeState += 8;

		if (noDiagonals) {
			if (edgeState == 1) {
				if (getCell(x + 1, y - 1) == PATH)
					return false;
				if (getCell(x + 1, y + 1) == PATH)
					return false;

				return true;
			} else if (edgeState == 2) {
				if (getCell(x - 1, y - 1) == PATH)
					return false;
				if (getCell(x - 1, y + 1) == PATH)
					return false;
				return true;
			} else if (edgeState == 4) {
				if (getCell(x - 1, y + 1) == PATH)
					return false;
				if (getCell(x + 1, y + 1) == PATH)
					return false;
				return true;
			} else if (edgeState == 8) {
				if (getCell(x - 1, y - 1) == PATH)
					return false;
				if (getCell(x + 1, y - 1) == PATH)
					return false;
				return true;
			}
			return false;
		} else { // Diagonal walls permitted
			if (edgeState == 1 || edgeState == 2 || edgeState == 4 || edgeState == 8)
				return true;
			return false;
		}

	}

	public int getCell(int x, int y) {
		if (x < 0 || x >= width || y < 0 || y >= height)
			return -1;
		else
			return maze[x][y];
	}

}
